﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Hockey.Shared;

namespace Hockey.Engine
{
    /// <summary>
    /// The main engine class
    /// </summary>
    public class Engine: IEngine
    {
        /// <summary>
        /// GameBoard reference
        /// </summary>
        private IBoardForEngine _board;

        /// <summary>
        /// The current engine path
        /// </summary>
        private Stack<eMoveDirection> _currentPath;

        /// <summary>
        /// The list of all paths - moves
        /// </summary>
        private List<PathRec> _moveList;

        /// <summary>
        /// How deep to go with the searching algorithm - prevention of long-running searches
        /// </summary>
        private const int maxDepth = 12;

        /// <summary>
        /// Background process
        /// </summary>
        private BackgroundWorker _worker;

        /// <summary>
        /// Engine difficulty set
        /// </summary>
        private eEngineLevel _engineLevel;

        /// <summary>
        /// Event signalling the computed move
        /// </summary>
        public event EventHandler<ComputedMoveEventArgs> MoveComputedEvent;

        /// <summary>
        /// Constructor
        /// </summary>
        public Engine()
        {
            _board = ServiceLocator.GetService(typeof(IBoardForEngine)) as IBoardForEngine;

            _currentPath = new Stack<eMoveDirection>();
            _moveList = new List<PathRec>();

            // init background process
            _worker = new BackgroundWorker();
            _worker.DoWork += EngineDoWork;
            _worker.RunWorkerCompleted += EngineComputationCompleted;
        }

        /// <summary>
        /// The main API method for computing the move
        /// </summary>
        public void StartMoveComputing(eEngineLevel engineLevel)
        {
            // clear the current path and the move list
            _currentPath.Clear();
            _moveList.Clear();
            _engineLevel = engineLevel;

            // run computation of move in the background thread
            _worker.RunWorkerAsync();
        }

        /// <summary>
        /// Engine process method
        /// </summary>
        void EngineDoWork(object sender, DoWorkEventArgs e)
        {
            // do the search
            DepthFirstSearch();
        }

        /// <summary>
        /// Calculation Completion method
        /// </summary>
        void EngineComputationCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // find the best move and return it
            ComputedMoveEventArgs args = new ComputedMoveEventArgs();
            args.Move = EvaluateMoveList(_engineLevel);

            // clear the current path and the move list
            _currentPath.Clear();
            _moveList.Clear();

            // invoke event if defined
            if (MoveComputedEvent != null)
            {
                MoveComputedEvent(this, args);
            }
        }

        /// <summary>
        /// Does the evaluation of the move list
        /// </summary>
        /// <returns></returns>
        private eMoveDirection[] EvaluateMoveList(eEngineLevel engineLevel)
        {
            // find the best row - the first filter
            int row = 0;
            if (_board.PuckDirection == ePuckDir.Up)
            {
                row = _moveList.Min(p => p.ReachedRow);              
            }
            else
            {
                row = _moveList.Max(p => p.ReachedRow);
            }

            var filteredList = _moveList.Where(p => p.ReachedRow == row).ToList();
            if (filteredList.Count == 1)
                return filteredList[0].ToArray();

            // the second filter - find the shortest or the longest move
            int length;            
            if (engineLevel == eEngineLevel.Novice)
            {
                length = filteredList.Min(p => p.Count);
            }
            else
            {
                length = filteredList.Max(p => p.Count);
            }

            // return the path
            return filteredList.First(p => p.Count == length).ToArray();
        }

        /// <summary>
        /// The main searching function
        /// </summary>
        private void DepthFirstSearch(int depth = maxDepth)
        {
            if (depth == 0)
                return;

            // get the list of all directions
            eMoveDirection[] dirs = _board.PuckNode.Bonds.Keys.ToArray();

            // iterate all directions
            foreach (var dir in dirs)
            {
                // check, if we can play in this direction
                if (_board.CanPlayInThisDirection(dir) == false)
                    continue;

                // play the move
                bool isFinal = false;
                _board.EnginePlay(dir, out isFinal);
                _currentPath.Push(dir);

                // if final, add the move record
                if (isFinal)
                {
                    AddCurrentPath();
                }
                else
                {
                    // else go deeper
                    DepthFirstSearch(depth - 1);
                }

                // undo move
                _currentPath.Pop();
                _board.EnginePlayBack();             
            } // foreach
        }

        /// <summary>
        /// Adds the current path to the node
        /// </summary>
        private void AddCurrentPath()
        {
            PathRec rec = new PathRec(_currentPath.Reverse(), _board.PuckNode.Coordinate.Row);
            _moveList.Add(rec);
        }

        /// <summary>
        /// Path record value
        /// </summary>
        private class PathRec : List<eMoveDirection>
        {
            public int ReachedRow
            { get; set; }

            public PathRec(IEnumerable<eMoveDirection> path, int reachedRow)
            {
                this.AddRange(path);
                ReachedRow = reachedRow;
            }
        }
    }
}
